<!doctype html>
<!--
Copyright 2016 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-capable" content="yes">

    <!-- Origin Trial Token, feature = WebVR (For Chrome M59+), origin = https://webvr.info, expires = 2017-10-13 -->
    <meta http-equiv="origin-trial" data-feature="WebVR (For Chrome M59+)" data-expires="2017-10-13" content="Aqw0TUvp8CwZmgIS50pM1blhzy05y7OkBeW3AQQGjer4KuKV0p13xfrRbe2bT8B1+DgRW0O3Dda0yOjcL46GBwgAAABMeyJvcmlnaW4iOiJodHRwczovL3dlYnZyLmluZm86NDQzIiwiZmVhdHVyZSI6IldlYlZSMS4xIiwiZXhwaXJ5IjoxNTA3ODU5MDE4fQ==">

    <title>XX - VR Controllers</title>

    <!--
      This sample demonstrates how to handle gamepads with 6DoF support, such as
      the Vive controllers, Oculus touch or Windows Motion controllers.
      PLEASE NOTE: The additions to the gamepad API used here are not yet
      part of the standard, and subject to change at any time!
    -->

    <style>
      #webgl-canvas {
        box-sizing: border-box;
        height: 100%;
        left: 0;
        margin: 0;
        position: absolute;
        top: 0;
        width: 100%;
      }
    </style>

    <!-- This entire block in only to facilitate dynamically enabling and
    disabling the WebVR polyfill, and is not necessary for most WebVR apps.
    If you want to use the polyfill in your app, just include the js file and
    everything will work the way you want it to by default. -->
    <script>
      var WebVRConfig = {
        // Prevents the polyfill from initializing automatically.
        DEFER_INITIALIZATION: true,
        // Ensures the polyfill is always active when initialized, even if the
        // native API is available. This is probably NOT what most pages want.
        POLYFILL_MODE: "ALWAYS",
        // Polyfill optimizations
        DIRTY_SUBMIT_FRAME_BINDINGS: true,
        BUFFER_SCALE: 0.75,
      };
    </script>
    <script src="js/third-party/webvr-polyfill.js"></script>
    <script src="js/third-party/wglu/wglu-url.js"></script>
    <script>
      // Dynamically turn the polyfill on if requested by the query args.
      if (WGLUUrl.getBool('polyfill', false)) {
        InitializeWebVRPolyfill();
      } else {
        // Shim for migration from older version of WebVR. Shouldn't be necessary for very long.
        InitializeSpecShim();
      }
    </script>
    <!-- End sample polyfill enabling logic -->

    <script src="js/third-party/gl-matrix-min.js"></script>

    <script src="js/third-party/wglu/wglu-debug-geometry.js"></script>
    <script src="js/third-party/wglu/wglu-program.js"></script>
    <script src="js/third-party/wglu/wglu-stats.js"></script>
    <script src="js/third-party/wglu/wglu-texture.js"></script>

    <script src="js/vr-cube-island.js"></script>
    <script src="js/vr-samples-util.js"></script>
  </head>
  <body>
    <canvas id="webgl-canvas"></canvas>
    <script>
      /* global mat4, vec3, VRCubeIsland, WGLUDebugGeometry, WGLUStats, WGLUTextureLoader, VRSamplesUtil */
      (function () {
      "use strict";

      var PLAYER_HEIGHT = 1.65;

      var vrDisplay = null;
      var frameData = null;
      var projectionMat = mat4.create();
      var viewMat = mat4.create();
      var poseMat = mat4.create();
      var gamepadMat = mat4.create();
      var gamepadMatTemp = mat4.create();
      var gamepadMatHandle = mat4.create();
      var gamepadColor = vec4.create();
      var standingPosition = vec3.create();
      var vrPresentButton = null;
      var orientation = [0, 0, 0, 1];
      var position = [0, 0, 0];

      // ===================================================
      // WebGL scene setup. This code is not WebVR specific.
      // ===================================================

      // WebGL setup.
      var gl = null;
      var cubeIsland = null;
      var stats = null;
      var debugGeom = null;

      function onContextLost( event ) {
        event.preventDefault();
        console.log( 'WebGL Context Lost.' );
        gl = null;
        cubeIsland = null;
        stats = null;
        debugGeom = null;
      }

      function onContextRestored( event ) {
        console.log( 'WebGL Context Restored.' );
        initWebGL(vrDisplay ? vrDisplay.capabilities.hasExternalDisplay : false);
      }

      var webglCanvas = document.getElementById("webgl-canvas");
      webglCanvas.addEventListener( 'webglcontextlost', onContextLost, false );
      webglCanvas.addEventListener( 'webglcontextrestored', onContextRestored, false );

      function initWebGL (preserveDrawingBuffer) {
        var glAttribs = {
          alpha: false,
          preserveDrawingBuffer: preserveDrawingBuffer
        };
        gl = webglCanvas.getContext("webgl", glAttribs);
        if (!gl) {
          gl = webglCanvas.getContext("experimental-webgl", glAttribs);
          if (!gl) {
            VRSamplesUtil.addError("Your browser does not support WebGL.");
            return;
          }
        }
        gl.clearColor(0.1, 0.2, 0.3, 1.0);
        gl.enable(gl.DEPTH_TEST);
        gl.enable(gl.CULL_FACE);

        var textureLoader = new WGLUTextureLoader(gl);
        var texture = textureLoader.loadTexture("media/textures/cube-sea.png");

        cubeIsland = new VRCubeIsland(gl, texture, 2, 2);

        var enablePerformanceMonitoring = WGLUUrl.getBool(
            'enablePerformanceMonitoring', false);
        stats = new WGLUStats(gl, enablePerformanceMonitoring);
        debugGeom = new WGLUDebugGeometry(gl);

        // Wait until we have a WebGL context to resize and start rendering.
        window.addEventListener("resize", onResize, false);
        onResize();
        window.requestAnimationFrame(onAnimationFrame);
      }

      // ================================
      // WebVR-specific code begins here.
      // ================================

      function onVRRequestPresent () {
        vrDisplay.requestPresent([{ source: webglCanvas }]).then(function () {
        }, function (err) {
          var errMsg = "requestPresent failed.";
          if (err && err.message) {
            errMsg += "<br/>" + err.message
          }
          VRSamplesUtil.addError(errMsg, 2000);
        });
      }

      function onVRExitPresent () {
        if (!vrDisplay.isPresenting)
          return;

        vrDisplay.exitPresent().then(function () {
        }, function () {
          VRSamplesUtil.addError("exitPresent failed.", 2000);
        });
      }

      function onVRPresentChange () {
        onResize();

        if (vrDisplay.isPresenting) {
          if (vrDisplay.capabilities.hasExternalDisplay) {
            VRSamplesUtil.removeButton(vrPresentButton);
            vrPresentButton = VRSamplesUtil.addButton("Exit VR", "E", "media/icons/cardboard64.png", onVRExitPresent);
          }
        } else {
          if (vrDisplay.capabilities.hasExternalDisplay) {
            VRSamplesUtil.removeButton(vrPresentButton);
            vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent);
          }
        }
      }

      if (navigator.getVRDisplays) {
        frameData = new VRFrameData();

        navigator.getVRDisplays().then(function (displays) {
          if (displays.length > 0) {
            vrDisplay = displays[displays.length - 1];
            vrDisplay.depthNear = 0.1;
            vrDisplay.depthFar = 1024.0;

            initWebGL(vrDisplay.capabilities.hasExternalDisplay);

            if (vrDisplay.stageParameters &&
                vrDisplay.stageParameters.sizeX > 0 &&
                vrDisplay.stageParameters.sizeZ > 0) {
              cubeIsland.resize(vrDisplay.stageParameters.sizeX, vrDisplay.stageParameters.sizeZ);
            }

            if (vrDisplay.capabilities.canPresent)
              vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent);

            // For the benefit of automated testing. Safe to ignore.
            if (vrDisplay.capabilities.canPresent && WGLUUrl.getBool('canvasClickPresents', false))
              webglCanvas.addEventListener("click", onVRRequestPresent, false);

            window.addEventListener('vrdisplaypresentchange', onVRPresentChange, false);
            window.addEventListener('vrdisplayactivate', onVRRequestPresent, false);
            window.addEventListener('vrdisplaydeactivate', onVRExitPresent, false);
          } else {
            initWebGL(false);
            VRSamplesUtil.addInfo("WebVR supported, but no VRDisplays found.", 3000);
          }
        }, function () {
          VRSamplesUtil.addError("Your browser does not support WebVR. See <a href='http://webvr.info'>webvr.info</a> for assistance.");
        });
      } else if (navigator.getVRDevices) {
        initWebGL(false);
        VRSamplesUtil.addError("Your browser supports WebVR but not the latest version. See <a href='http://webvr.info'>webvr.info</a> for more info.");
      } else {
        initWebGL(false);
        VRSamplesUtil.addError("Your browser does not support WebVR. See <a href='http://webvr.info'>webvr.info</a> for assistance.");
      }

      function onResize () {
        if (vrDisplay && vrDisplay.isPresenting) {
          var leftEye = vrDisplay.getEyeParameters("left");
          var rightEye = vrDisplay.getEyeParameters("right");

          webglCanvas.width = Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2;
          webglCanvas.height = Math.max(leftEye.renderHeight, rightEye.renderHeight);
        } else {
          webglCanvas.width = webglCanvas.offsetWidth * window.devicePixelRatio;
          webglCanvas.height = webglCanvas.offsetHeight * window.devicePixelRatio;
        }
      }

      function onClick () {
        // Reset the background color to a random value
        gl.clearColor(
            Math.random() * 0.5,
            Math.random() * 0.5,
            Math.random() * 0.5, 1.0);
      }
      VRSamplesUtil.addVRClickListener(onClick);
      webglCanvas.addEventListener("click", onClick, false);

      function getStandingViewMatrix (out, view) {
        if (vrDisplay.stageParameters) {
          mat4.invert(out, vrDisplay.stageParameters.sittingToStandingTransform);
          mat4.multiply(out, view, out);
        } else {
          mat4.identity(out);
          mat4.translate(out, out, [0, PLAYER_HEIGHT, 0]);
          mat4.invert(out, out);
          mat4.multiply(out, view, out);
        }
      }

      function getPoseMatrix (out, pose, isGamepad) {
        orientation = pose && pose.orientation;
        position = pose && pose.position;
        if (!orientation) { orientation = [0, 0, 0, 1]; }
        if (!position) {
          // If this is a gamepad without a pose set it out in front of us so
          // we can see it.
          position = isGamepad ? [0.1, -0.1, -0.5] : [0, 0, 0];
        }

        if (vrDisplay.stageParameters) {
          mat4.fromRotationTranslation(out, orientation, position);
          mat4.multiply(out, vrDisplay.stageParameters.sittingToStandingTransform, out);
        } else {
          vec3.add(standingPosition, position, [0, PLAYER_HEIGHT, 0]);
          mat4.fromRotationTranslation(out, orientation, standingPosition);
        }
      }

      var hmdDebugMat = mat4.create();
      var hmdDebugOrigin = vec4.create();
      var hmdDebugCounterRotation = vec4.create();
      var controllerDebugMat = mat4.create();
      var controllerDebugOrigin = vec4.create();
      var controllerDebugCounterRotation = vec4.create();
      var arrowColor = vec4.create();

      function renderSceneView (projection, view, hmdPose, gamepads) {
        cubeIsland.render(projection, view, stats);

        // Draw head-locked standing HMD coordinate system for debugging.
        mat4.identity(hmdDebugMat);
        debugGeom.bind(projection, hmdDebugMat);

        quat.invert(hmdDebugCounterRotation,
                    hmdPose.orientation || [0, 0, 0, 1]);
        mat4.fromRotationTranslation(hmdDebugMat,
                                     hmdDebugCounterRotation,
                                     [-0.1, 0.17, -0.5]);
        debugGeom.drawCoordinateAxes(hmdDebugMat);
        // Show angular velocity from gyro if available
        if (hmdPose.angularVelocity) {
          vec4.set(arrowColor, 0.7, 0.5, 0.3, 1);
          debugGeom.drawArrow(hmdDebugMat, hmdPose.angularVelocity, arrowColor);
        }
        // Show linear acceleration if available
        if (hmdPose.linearAcceleration) {
          vec4.set(arrowColor, 0.3, 0.2, 0.7, 1);
          // Linear acceleration is large since it contains 9.81 m/s^2 gravity,
          // scale it down so that it's more visible.
          debugGeom.drawArrow(hmdDebugMat, hmdPose.linearAcceleration, arrowColor, 0.15);
        }

        debugGeom.bind(projection, view);

        // Render every gamepad with a pose we found
        for (var i = 0; i < gamepads.length; ++i) {
          var gamepad = gamepads[i];

          // Draw X/Y/Z axes for un-rotated controller standing coordinate system.
          getPoseMatrix(controllerDebugMat, {
            position: gamepad.pose && gamepad.pose.position,
            orientation: [0, 0, 0, 1]},
            true);
          debugGeom.drawCoordinateAxes(controllerDebugMat);

          // Show angular velocity from gyro if available
          if (gamepad.pose && gamepad.pose.angularVelocity) {
            vec4.set(arrowColor, 0.7, 0.5, 0.3, 1);
            debugGeom.drawArrow(controllerDebugMat, gamepad.pose.angularVelocity, arrowColor);
          }

          // Show linear acceleration if available
          if (gamepad.pose && gamepad.pose.linearAcceleration) {
            //console.log('accel', gamepad.pose.linearAcceleration);
            vec4.set(arrowColor, 0.3, 0.2, 0.7, 1);
            // Linear acceleration is large since it contains 9.81 m/s^2 gravity,
            // scale it down so that it's more visible.
            debugGeom.drawArrow(controllerDebugMat, gamepad.pose.linearAcceleration, arrowColor, 0.15);
          }

          // Loop through all the gamepad's axes and scale the gamepad geometry
          // by their value.
          var scale = [0.1, 0.1, 0.1];
          for (var j = 0; j < gamepad.axes.length; ++j) {
            switch (j%3) {
              case 0:
                scale[0] *= 1.0 + gamepad.axes[j];
                break;
              case 1:
                scale[1] *= 1.0 + gamepad.axes[j];
                break;
              case 2:
                scale[2] *= 1.0 + gamepad.axes[j];
                break;
            }
          }

          // Because this sample is done in standing space we need to apply
          // the same transformation to the gamepad pose as we did the
          // VRDisplay's pose.
          getPoseMatrix(gamepadMat, gamepad.pose, true);

          // Scaled down to from 1 meter to be something closer to the size of
          // a hand.
          mat4.scale(gamepadMat, gamepadMat, scale);

          // Rotate -90 deg so the point of the cone faces "forward"
          mat4.rotateX(gamepadMat, gamepadMat, -Math.PI * 0.5);

          // Show the gamepad's cube as red if any buttons are pressed, gray
          // otherwise.
          vec4.set(gamepadColor, 0.7, 0.7, 0.7, 1);
          for (var j = 0; j < gamepad.buttons.length; ++j) {
            if (gamepad.buttons[j].pressed) {
              vec4.set(gamepadColor, gamepad.buttons[j].value, 0, 0, 1);
              break;
            }
          }

          debugGeom.drawConeWithMatrix(gamepadMat, gamepadColor);

          // Draw a "handle" for the gamepad
          mat4.identity(gamepadMatTemp);
          mat4.translate(gamepadMatTemp, gamepadMatTemp, [0, -0.5, -0.3]);
          mat4.rotateX(gamepadMatTemp, gamepadMatTemp, -Math.PI * 0.2);
          mat4.scale(gamepadMatTemp, gamepadMatTemp, [0.25, 0.25, 0.5]);

          mat4.multiply(gamepadMatHandle, gamepadMat, gamepadMatTemp);

          debugGeom.drawBoxWithMatrix(gamepadMatHandle, gamepadColor);

        }
      }

      function onAnimationFrame (t) {
        // do not attempt to render if there is no available WebGL context
        if (!gl || !stats || !cubeIsland || !debugGeom) {
          return;
        }
        stats.begin();

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        if (vrDisplay) {
          vrDisplay.requestAnimationFrame(onAnimationFrame);

          // Loop over every gamepad and if we find any that have a pose use it.
          var vrGamepads = [];

          var gamepads = navigator.getGamepads();
          for (var i = 0; i < gamepads.length; ++i) {
            var gamepad = gamepads[i];
            // The array may contain undefined gamepads, so check for that as
            // well as a non-null pose. VR clicker devices such as the Carboard
            // touch handler for Daydream have a displayId but no pose.
            if (gamepad) {
              if (gamepad.pose || gamepad.displayId)
                vrGamepads.push(gamepad);

              if ("hapticActuators" in gamepad && gamepad.hapticActuators.length > 0) {
                for (var j = 0; j < gamepad.buttons.length; ++j) {
                  if (gamepad.buttons[j].pressed) {
                    // Vibrate the gamepad using to the value of the button as
                    // the vibration intensity.
                    gamepad.hapticActuators[0].pulse(gamepad.buttons[j].value, 100);
                    break;
                  }
                }
              }
            }
          }

          vrDisplay.getFrameData(frameData);

          if (vrDisplay.isPresenting) {
            gl.viewport(0, 0, webglCanvas.width * 0.5, webglCanvas.height);
            getStandingViewMatrix(viewMat, frameData.leftViewMatrix);
            renderSceneView(frameData.leftProjectionMatrix, viewMat, frameData.pose, vrGamepads);

            gl.viewport(webglCanvas.width * 0.5, 0, webglCanvas.width * 0.5, webglCanvas.height);
            getStandingViewMatrix(viewMat, frameData.rightViewMatrix);
            renderSceneView(frameData.rightProjectionMatrix, viewMat, frameData.pose, vrGamepads);

            vrDisplay.submitFrame();
          } else {
            gl.viewport(0, 0, webglCanvas.width, webglCanvas.height);
            mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0);
            getStandingViewMatrix(viewMat, frameData.leftViewMatrix);
            renderSceneView(projectionMat, viewMat, frameData.pose, vrGamepads);
            stats.renderOrtho();
          }
        } else {
          window.requestAnimationFrame(onAnimationFrame);

          // No VRDisplay found.
          gl.viewport(0, 0, webglCanvas.width, webglCanvas.height);
          mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0);
          mat4.identity(viewMat);
          mat4.translate(viewMat, viewMat, [0, -PLAYER_HEIGHT, 0]);
          cubeIsland.render(projectionMat, viewMat, stats);

          stats.renderOrtho();
        }

        stats.end();
      }
      })();
    </script>
  </body>
</html>
